package Domaine;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import Domaine.Noeud;
import Domaine.Segment;
import Domaine.VehiculeUrgence;
import Erreurs.ObjetExistantError;
import Erreurs.ObjetInexistantError;
import Simulateur.Strategie;


/**
 * 
 * @author Noyau
 * @version 1.0
 * @since 2013-11-12
 * File Reseau.java
 *
 */

public class Reseau implements Cloneable
{
	private ArrayList<Noeud> _listeNoeuds;
	private ArrayList<Segment> _listeSegments;
	private VehiculeUrgence _vehiculeUrgence;
	private Strategie _strategie;

	/**
	 * Constructeur de base sans valeur
	 */
	public Reseau()
	{
		this._strategie = null;
		this._listeNoeuds = new ArrayList<Noeud>();
		this._listeSegments = new ArrayList<Segment>();
	}
	
	public Reseau clone() 
	{
		Reseau copy = null;
		try
		{
			copy = (Reseau) super.clone();
		} catch(CloneNotSupportedException copyExcept)
		{
			copyExcept.printStackTrace(System.err);
		}
	
		//copie des noeuds
		if (_listeNoeuds == null)
		{
			copy._listeNoeuds = null;
		}
		else
		{
			copy._listeNoeuds = new ArrayList<Noeud>();
			for(Noeud noeud: _listeNoeuds) copy._listeNoeuds.add(noeud.clone());
		}
		
	    //copie des segments
		if (_listeSegments == null)
		{
			copy._listeSegments = null;
		}
		else
		{
			copy._listeSegments = new ArrayList<Segment>();
			for(Segment segment: _listeSegments) copy._listeSegments.add(segment.clone());
		}
		
  	  	if (_strategie == null)
  	  	{
  	  		copy._strategie =  null;
  	  	}
  	  	else
  	  	{
  	  		copy._strategie =  _strategie.clone();
  	  	}
  	  	
  	  	if (_vehiculeUrgence == null)
	  	{
	  		copy._vehiculeUrgence =  null;
	  	}
	  	else
	  	{
	  		copy._vehiculeUrgence =  _vehiculeUrgence.clone();
	  	}
	    
		return copy;
	}
		
	/**
	 * Obenir la liste des noeuds
	 * @return List<Noeud> : La liste des noeuds du r�seau
	 */
	public ArrayList<Noeud> getListeNoeuds() 
	{
		return this._listeNoeuds;
	}

	/**
	 * Modifier la liste des noeuds
	 * @param aListeNoeuds : La nouvelle liste de noeuds
	 */
	public void setListeNoeuds(ArrayList<Noeud> aListeNoeuds) 
	{
		_listeNoeuds = aListeNoeuds;
	}

	/**
	 * Obtenir la liste des segments du r�seau
	 * @return List<Segment>
	 */
	public ArrayList<Segment> getListeSegments()
	{
		return _listeSegments;
	}

	/**
	 * Modifier la liste des segments du r�seau
	 * @param aListeSegments
	 */
	public void setListeSegments(ArrayList<Segment> aListeSegments) 
	{
		_listeSegments = aListeSegments;
	}

	/**
	 *  @return VehiculeUrgence : Le v�hicule d'urgence du r�seau
	 */
	public VehiculeUrgence getVehiculeUrgence() 
	{
		return this._vehiculeUrgence;
	}

	/**
	 * Modifier le v�hicule d'urgence du r�seau
	 * @param aVehiculeUrgence : Le v�hicule d'urgence 
	 */
	public void setVehiculeUrgence(VehiculeUrgence aVehiculeUrgence) 
	{
		this._vehiculeUrgence = aVehiculeUrgence;
	}


	/**
	 * 
	 * @param noeud : Le noeud a ajouter dans la liste de noeuds du r�seau
	 */
	public void ajoutNoeud(Noeud noeud) throws ObjetExistantError
	{
		if(this._listeNoeuds.contains(noeud))
		{
			throw new ObjetExistantError("Le noeud existe d�j�");
		}
		_listeNoeuds.add(noeud);
	}
	
	// Retourne null si l'�l�ment n'existe pas.
	public Noeud getNoeud(int x, int y){

		for(Noeud n: this._listeNoeuds){
			if(n.getX() == x && n.getY() == y){
				return n;
			}
		}
		return null;
	}
	
	public boolean exists(Segment s){
		boolean b = false;
		
		for(Segment seg : this._listeSegments){
			if(seg.getNoeudDepart().getX() == s.getNoeudDepart().getX() &&
					seg.getNoeudDepart().getY() == s.getNoeudDepart().getY() &&
					seg.getNoeudFin().getX() == s.getNoeudFin().getX() &&
					seg.getNoeudFin().getY() == s.getNoeudFin().getY()
					||
					seg.getNoeudFin().getX() == s.getNoeudDepart().getX() &&
					seg.getNoeudFin().getY() == s.getNoeudDepart().getY() &&
					seg.getNoeudDepart().getX() == s.getNoeudFin().getX() &&
					seg.getNoeudDepart().getY() == s.getNoeudFin().getY()){
				b = true;
			}
		}
		
		return b;
	}
	/**
	 * 
	 * @param urgence : L'urgence a retirer de la liste
	 */
	public void supprimerNoeud(Noeud noeud) throws ObjetInexistantError
	{
		if (!(_listeNoeuds.contains(noeud)))
		{
			throw new ObjetInexistantError("L'urgence est inexistante dans la liste du Noeud");
		}
		
		_listeNoeuds.remove(noeud);
	}
	
	public void ajoutSegment(Segment seg) throws ObjetExistantError
	{
		if(this._listeSegments.contains(seg)){
			throw new ObjetExistantError("Le segment existe d�j�.");
		}
		this._listeSegments.add(seg);
	}
	
	// M�thode de suppression d'un segment.
	public void supprimerSegment(Segment s) throws ObjetInexistantError{
		
		if(!this._listeSegments.contains(s)){
			throw new ObjetInexistantError("Le segment n'existe pas");
		}
		this._listeSegments.remove(s);
	}
	


	/**
	 * Obtenir la strat�gie du r�seau actuellement s�lectionn�
	 * @return Strategie
	 */
	public Strategie get_strategie() 
	{
		return _strategie;
	}

	/**
	 * Modifier la strat�gie du r�seau
	 * @param _strategie
	 */
	public void set_strategie(Strategie _strategie) 
	{
		this._strategie = _strategie;
	}
	
	
	public List<Urgence> getListeUrgence() {
		
		List<Urgence> urgences = new ArrayList<Urgence>();
		List<Urgence> urgencesNoeud = new ArrayList<Urgence>();
		
		for (Noeud noeud : _listeNoeuds) 
		{
			urgencesNoeud = noeud.getListeUrgences();
			
			for (Urgence urgence : urgencesNoeud) 
			{
				urgences.add(urgence);
			}
		}
		
		return urgences;
	}

	
	public Segment getSegment(Noeud depart, Noeud fin) {
		
		for (Segment segment : _listeSegments) {
			if ((segment.getNoeudDepart().equals(depart) && segment.getNoeudFin().equals(fin)) ||
					(segment.getNoeudDepart().equals(fin) && segment.getNoeudFin().equals(depart))) {
				return segment;
			}
		}
		
		return null;
	}
	
	public List<Noeud> obtenirCheminPlusCourt(Noeud d, Noeud f) {
		
		Noeud debut = obtenirNoeud(d);
		Noeud fin = obtenirNoeud(f);
		
		ArrayList<Noeud> Q = new ArrayList<Noeud>();
		
		ArrayList<Double> dist = new ArrayList<Double>(_listeNoeuds.size());
		ArrayList<Boolean> visited = new ArrayList<Boolean>(_listeNoeuds.size());
		ArrayList<Integer> previous = new ArrayList<Integer>(_listeNoeuds.size());
		
		for (int i = 0; i < _listeNoeuds.size(); i++) {
			dist.add(Double.MAX_VALUE);
			visited.add(false);
			previous.add(-1);
		}   
		
		int s = _listeNoeuds.indexOf(debut);
		dist.set(s, 0.0d);
		Q.add(debut);
		
		while (!Q.isEmpty()) {
			
			Noeud n = getMinDist(Q, dist, visited);
			
			if (n == fin) {
				break;
			}
			
			Q.remove(n);
			int u = _listeNoeuds.indexOf(n);
			
			visited.set(u, true);
			
			for (Segment segment : getSuccesseurs(n)) {
				double alt = dist.get(u) + segment.get_longueur();
				Noeud noeudFin = segment.getNoeudDepart().equals(n) ? segment.getNoeudFin() : segment.getNoeudDepart();
				noeudFin = obtenirNoeud(noeudFin);
				int v = _listeNoeuds.indexOf(noeudFin);
				if (alt < dist.get(v) && !visited.get(v)) {
					dist.set(v, alt);
					previous.set(v, u);
					Q.add(noeudFin);
				}
			}

		}
		
		ArrayList<Noeud> S = new ArrayList<Noeud>();
		int u = _listeNoeuds.indexOf(fin);
		while (u != -1) {
			S.add(_listeNoeuds.get(u));
			u = previous.get(u);
		}
		
		Collections.reverse(S);
		return S;
	}
	
	private Noeud obtenirNoeud(Noeud n) {
		
		for (Noeud noeud : _listeNoeuds) {
			if (n.equals(noeud))
				return noeud;
		}
		
		return null;
	}
	
	
	private Noeud getMinDist(ArrayList<Noeud> Q, ArrayList<Double> dist, ArrayList<Boolean> visited) {
		
		int index = 0;
		Double distance = Double.MAX_VALUE;
		
		for (int i = 0; i < Q.size(); i++) {
			Noeud courant = Q.get(i);
			int s = _listeNoeuds.indexOf(courant);
			if (!visited.get(s) && distance > dist.get(s)) {
				index = i;
				distance = dist.get(s);
			}
		}
		
		return Q.get(index);
	}
	
	public ArrayList<Segment> getSuccesseurs(Noeud source) {
		
		ArrayList<Segment> successeurs = new ArrayList<Segment>();
		
		for (Segment segment : _listeSegments) {
			if (segment.getNoeudDepart().equals(source)) {
				successeurs.add(segment);
			}
			if(segment.getNoeudFin().equals(source)) {
				successeurs.add(segment);
			}
		}
		
		return successeurs;
	}
	
}